#include <cmath>
#include <Common/platform.h>
#include <Utility/limit.h>
#include "AABB.h"

namespace shermit {
	// AABB2

	template<typename Float_t>
	GENERAL void AABB2<Float_t>::merge(const AABB2<Float_t>& b) {
		ptMin = vec_t(fmin(ptMin.x, b.ptMin.x), fmin(ptMin.y, b.ptMin.y));
		ptMax = vec_t(fmax(ptMax.x, b.ptMax.x), fmax(ptMax.y, b.ptMax.y));
	}

	template<typename Float_t>
	GENERAL void AABB2<Float_t>::merge(const vec_t& b) {
		ptMin = vec_t(fmin(ptMin.x, b.x), fmin(ptMin.y, b.y));
		ptMax = vec_t(fmax(ptMax.x, b.x), fmax(ptMax.y, b.y));
	}

	template<typename Float_t>
	GENERAL void AABB2<Float_t>::merge(const Float_t x, const Float_t y) {
		ptMin = vec_t(fmin(ptMin.x, x), fmin(ptMin.y, y));
		ptMax = vec_t(fmax(ptMax.x, x), fmax(ptMax.y, y));
	}

	template<typename Float_t>
	GENERAL bool AABB2<Float_t>::overlap(const AABB2<Float_t>& b) const {
		if (b.ptMin.x > ptMax.x || b.ptMax.x < ptMin.x) return false;
		if (b.ptMin.y > ptMax.y || b.ptMax.y < ptMin.y) return false;
		return true;
	}

	template<typename Float_t>
	GENERAL bool AABB2<Float_t>::contain(const AABB2<Float_t>& b) const {
		return 
			ptMin.x <= b.ptMin.x && 
			ptMin.y <= b.ptMin.y && 
			ptMax.x >= b.ptMax.x &&
			ptMax.y >= b.ptMax.y;
	}

	template<typename Float_t>
	GENERAL void AABB2<Float_t>::reset() {
		const Float_t _max = NumericLimit<Float_t>::max();
		ptMin = vec_t(_max, _max);
		ptMax = vec_t(-_max, -_max);
	}

	__host__ std::string AABB2<float>::toString() {
		char content[100];
		sprintf(content, "{(%.5f,%.5f), (%.5f,%.5f)}", ptMin.x, ptMin.y, ptMax.x, ptMax.y);
		return std::string(content);
	}

	__host__ std::string AABB2<double>::toString() {
		char content[100];
		sprintf(content, "{(%.5lf,%.5lf), (%.5lf,%.5lf)}", ptMin.x, ptMin.y, ptMax.x, ptMax.y);
		return std::string(content);
	}

	template<typename Float_t>
	void AABB2_SOA<Float_t>::allocateHost(size_t size) {
		minx = new Float_t[size];
		miny = new Float_t[size];
		maxx = new Float_t[size];
		maxy = new Float_t[size];
	}

	template<typename Float_t>
	void AABB2_SOA<Float_t>::deallocateHost() {
		delete[] minx;
		delete[] miny;
		delete[] maxx;
		delete[] maxy;
	}

	template<typename Float_t>
	void AABB2_SOA<Float_t>::resetHost(size_t size) {
		const Float_t _max = NumericLimit<Float_t>::max();
		std::fill(minx, minx + size, _max);
		std::fill(miny, miny + size, _max);
		std::fill(maxx, maxx + size, -_max);
		std::fill(maxy, maxy + size, -_max);
	}

#if enable_cuda
	template<typename Float_t>
	void AABB2_SOA<Float_t>::allocateDevice(size_t size) {
		checkCudaErrors(GPUMalloc(minx, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(miny, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(maxx, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(maxy, sizeof(Float_t) * size));
	}

	template<typename Float_t>
	void AABB2_SOA<Float_t>::deallocateDevice() {
		checkCudaErrors(cudaFree(minx));
		checkCudaErrors(cudaFree(miny));
		checkCudaErrors(cudaFree(maxx));
		checkCudaErrors(cudaFree(maxy));
	}

	template<typename Float_t>
	void AABB2_SOA<Float_t>::resetDevice(size_t size) {
		const Float_t _max = NumericLimit<Float_t>::max();

		thrust::fill(thrust::device_ptr<Float_t>(minx), thrust::device_ptr<Float_t>(minx) + size, _max);
		thrust::fill(thrust::device_ptr<Float_t>(miny), thrust::device_ptr<Float_t>(miny) + size, _max);
		thrust::fill(thrust::device_ptr<Float_t>(maxx), thrust::device_ptr<Float_t>(maxx) + size, -_max);
		thrust::fill(thrust::device_ptr<Float_t>(maxy), thrust::device_ptr<Float_t>(maxy) + size, -_max);
	}
#endif

	// AABB3

	template<typename Float_t>
	GENERAL void AABB3<Float_t>::merge(const AABB3<Float_t>& b) {
		ptMin = vec_t(fmin(ptMin.x, b.ptMin.x), fmin(ptMin.y, b.ptMin.y), fmin(ptMin.z, b.ptMin.z));
		ptMax = vec_t(fmax(ptMax.x, b.ptMax.x), fmax(ptMax.y, b.ptMax.y), fmax(ptMax.z, b.ptMax.z));
	}

	template<typename Float_t>
	GENERAL void AABB3<Float_t>::merge(const vec_t& b) {
		ptMin = vec_t(fmin(ptMin.x, b.x), fmin(ptMin.y, b.y), fmin(ptMin.z, b.z));
		ptMax = vec_t(fmax(ptMax.x, b.x), fmax(ptMax.y, b.y), fmax(ptMax.z, b.z));
	}

	template<typename Float_t>
	GENERAL void AABB3<Float_t>::merge(const Float_t x, const Float_t y, const Float_t z) {
		ptMin = vec_t(fmin(ptMin.x, x), fmin(ptMin.y, y), fmin(ptMin.z, z));
		ptMax = vec_t(fmax(ptMax.x, x), fmax(ptMax.y, y), fmax(ptMax.z, z));
	}

	template<typename Float_t>
	GENERAL bool AABB3<Float_t>::overlap(const AABB3<Float_t>& b) const {
		if (b.ptMin.x > ptMax.x || b.ptMax.x < ptMin.x) return false;
		if (b.ptMin.y > ptMax.y || b.ptMax.y < ptMin.y) return false;
		if (b.ptMin.z > ptMax.z || b.ptMax.z < ptMin.z) return false;
		return true;
	}

	template<typename Float_t>
	GENERAL bool AABB3<Float_t>::contain(const AABB3<Float_t>& b) const {
		return
			ptMin.x <= b.ptMin.x &&
			ptMin.y <= b.ptMin.y &&
			ptMin.z <= b.ptMin.z &&
			ptMax.x >= b.ptMax.x &&
			ptMax.y >= b.ptMax.y &&
			ptMax.z >= b.ptMax.z;
	}

	template<typename Float_t>
	GENERAL void AABB3<Float_t>::reset() {
		const Float_t _max = NumericLimit<Float_t>::max();
		ptMin = vec_t(_max, _max, _max);
		ptMax = vec_t(-_max, -_max, -_max);
	}

	__host__ std::string AABB3<float>::toString() {
		char content[100];
		sprintf(content, "{(%.5f,%.5f,%.5f), (%.5f,%.5f,%.5f)}", ptMin.x, ptMin.y, ptMin.z, ptMax.x, ptMax.y, ptMax.z);
		return std::string(content);
	}

	__host__ std::string AABB3<double>::toString() {
		char content[100];
		sprintf(content, "{(%.5lf,%.5lf,%.5lf), (%.5lf,%.5lf,%.5lf)}", ptMin.x, ptMin.y, ptMin.z, ptMax.x, ptMax.y, ptMax.z);
		return std::string(content);
	}

	template<typename Float_t>
	void AABB3_SOA<Float_t>::allocateHost(size_t size) {
		minx = new Float_t[size];
		miny = new Float_t[size];
		minz = new Float_t[size];
		maxx = new Float_t[size];
		maxy = new Float_t[size];
		maxz = new Float_t[size];
	}

	template<typename Float_t>
	void AABB3_SOA<Float_t>::deallocateHost() {
		delete[] minx;
		delete[] miny;
		delete[] minz;
		delete[] maxx;
		delete[] maxy;
		delete[] maxz;
	}

	template<typename Float_t>
	void AABB3_SOA<Float_t>::resetHost(size_t size) {
		const Float_t _max = NumericLimit<Float_t>::max();
		std::fill(minx, minx + size, _max);
		std::fill(miny, miny + size, _max);
		std::fill(minz, minz + size, _max);
		std::fill(maxx, maxx + size, -_max);
		std::fill(maxy, maxy + size, -_max);
		std::fill(maxz, maxz + size, -_max);
	}

#if enable_cuda
	template<typename Float_t>
	void AABB3_SOA<Float_t>::allocateDevice(size_t size) {
		checkCudaErrors(GPUMalloc(minx, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(miny, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(minz, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(maxx, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(maxy, sizeof(Float_t) * size));
		checkCudaErrors(GPUMalloc(maxz, sizeof(Float_t) * size));
	}

	template<typename Float_t>
	void AABB3_SOA<Float_t>::deallocateDevice() {
		checkCudaErrors(cudaFree(minx));
		checkCudaErrors(cudaFree(miny));
		checkCudaErrors(cudaFree(minz));
		checkCudaErrors(cudaFree(maxx));
		checkCudaErrors(cudaFree(maxy));
		checkCudaErrors(cudaFree(maxz));
	}

	template<typename Float_t>
	void AABB3_SOA<Float_t>::resetDevice(size_t size) {
		const Float_t _max = NumericLimit<Float_t>::max();

		thrust::fill(thrust::device_ptr<Float_t>(minx), thrust::device_ptr<Float_t>(minx) + size, _max);
		thrust::fill(thrust::device_ptr<Float_t>(miny), thrust::device_ptr<Float_t>(miny) + size, _max);
		thrust::fill(thrust::device_ptr<Float_t>(minz), thrust::device_ptr<Float_t>(minz) + size, _max);
		thrust::fill(thrust::device_ptr<Float_t>(maxx), thrust::device_ptr<Float_t>(maxx) + size, -_max);
		thrust::fill(thrust::device_ptr<Float_t>(maxy), thrust::device_ptr<Float_t>(maxy) + size, -_max);
		thrust::fill(thrust::device_ptr<Float_t>(maxz), thrust::device_ptr<Float_t>(maxz) + size, -_max);
	}
#endif

	template class AABB2<float>;
	template class AABB2<double>;
	template class AABB2_SOA<float>;
	template class AABB2_SOA<double>;

	template class AABB3<float>;
	template class AABB3<double>;
	template class AABB3_SOA<float>;
	template class AABB3_SOA<double>;
}
